# @Time : 2025-01-16 10:58
# @Author : Fioman 
# @Phone : 13149920693
"""
相机工具
"""
import os
import time

import cv2 as cv
import gevent
import numpy as np

import app_config
from common.exception_helper import error_handler_produce
from common.file_dir_helper import save_joint_image, delete_exist_index_image, open_dir
from common.produce_logger import debug_produce
from db_tool.db_config import TableName
from db_tool.db_handler import DbHandler
from drivers.cut_plc import CutPlc
from params.camera_params import CameraParams
from personal_tool.data_result import DataResult
from vision_process.calc_config import TotalboardResult
from vision_process.totalboard_check import find_totalboard
from web_tool.send_to_web_handler import update_tool_data_in_web, image_to_string
from web_tool.tool_data_model import ToolDataType

cp = CameraParams()
cutPlc = CutPlc()
db = DbHandler()


class CameraTool:
    def __init__(self, leftCam, rightCam):
        self.leftCam = leftCam
        self.rightCam = rightCam
        self.reaload_image_joint_params()
        self.leftImg = None
        self.rightImg = None

    @error_handler_produce
    def reaload_image_joint_params(self):
        result = db.get_data_one(TableName.imageJointParams, query={"key": "1"})
        if not result.state:
            data = {}
        else:
            data = result.data
        self.leftCamStartOffset = int(data.get("leftCamStartOffset"))  # 左相机的左边截掉的像素数
        self.leftCamEndOffset = int(data.get("leftCamEndOffset"))  # 左相机右边截掉的像素数
        self.rightCamStartOffset = int(data.get("rightCamStartOffset"))  # 右相机左边截掉的像素数
        self.rightCamEndOffset = int(data.get("rightCamEndOffset"))  # 右相机右边截掉的像素数
        self.leftCamHOffset = int(data.get("leftCamHOffset"))  # 左相机的高度移动的像素数,负数表示上移,正数表示向下移动
        self.inputWidth = data.get("inputWidth")
        self.inputHeight = data.get("inputHeight")
        debug_produce(f"imageJointParams加载成功: {data}")

    @staticmethod
    def get_camera_params_from_web():
        """
        请求相机参数
        :return:
        """
        update_tool_data_in_web(ToolDataType.cameraParams)
        return DataResult(state=True)

    @staticmethod
    def start_cut_plc_from_web() -> DataResult:
        """
        请求启动plc
        :return:
        """
        res = cutPlc.is_run_state()
        if not res.state:
            return res
        if res.data:
            res = cutPlc.set_allow_plc_work()
            if not res.state:
                return res
        res = cutPlc.start_cut_plc()
        if not res.state:
            return res

    @staticmethod
    def stop_cut_plc_from_web() -> DataResult:
        """
        请求停止plc
        :return:
        """
        return cutPlc.stop_cut_plc()

    @staticmethod
    def reset_cut_plc_from_web() -> DataResult:
        """
        请求复位plc
        :return:
        """
        return cutPlc.reset_cut_plc()

    def get_image_for_joint_from_web(self) -> DataResult:
        """
        界面上点击抓取图片的逻辑
        :return:
        """
        cutPlc.set_allow_plc_work()
        result = DataResult()
        start = time.time()
        while True:
            if time.time() - start > 30:
                errorInfo = "未检测到Plc的允许拍照信号,请确认物料在拍照起点"
                debug_produce(errorInfo)
                result.info = errorInfo
                return result
            res = cutPlc.get_allow_take_pic()
            if not res.state:
                return res
            if res.data:
                break
            else:
                gevent.sleep(0.5)

        # 走到这里就证明了res.data = True
        imgResult = self.get_image_from_line_cam()
        if not imgResult.state:
            return imgResult
        jointImg = imgResult.data
        save_joint_image(jointImg)
        # 后续再去计算这些
        imageSend = image_to_string(jointImg)
        result.data = imageSend
        result.state = True
        return result

    @error_handler_produce
    def re_start_line_camera(self):
        """
        重新打开线阵相机
        :return:
        """
        self.leftCam.stop_grabbing()
        self.rightCam.stop_grabbing()
        gevent.sleep(1)
        self.leftCam.start_grabbing()
        self.rightCam.start_grabbing()

    @error_handler_produce
    def get_image_from_line_cam(self) -> DataResult:
        """
        从线阵相机里获取图片,拼接后的图片
        :return:
        """
        # 4 秒之后再去获取图片
        result = DataResult()
        opRes = cutPlc.set_ready_to_take_pic()
        if not opRes.state:
            return opRes
        gevent.sleep(1)
        self.leftCam.clear_buffer()
        self.rightCam.clear_buffer()
        cutPlc.clear_allow_get_pic_state()
        retLeft, leftImage = self.leftCam.get_image_line()
        if retLeft != 0:
            result.info = "左相机获取图片失败: {}".format(leftImage)
            return result
        retRight, rightImage = self.rightCam.get_image_line()
        if retRight != 0:
            result.info = "右相机获取图片失败: {}".format(rightImage)
            return result
        self.leftImg, self.rightImg = (cv.flip(leftImage, 1),
                                       rightImage)
        jointImage = self.get_jont_image()
        result.state = True
        result.data = jointImage
        return result

    def re_get_image_for_joint_from_web(self):
        """
        重新获取拼接后的图像
        :return:
        """
        res = cutPlc.re_get_img()
        if not res.state:
            return res

        return self.get_image_for_joint_from_web()

    def get_jont_image(self):
        """
        根据拼接的餐胡,获取三个相机拼接后的图像
        :return:
        """
        leftStartX, leftEndX = self.leftCamStartOffset, self.leftImg.shape[1] - self.leftCamEndOffset
        rightStartX, rightEndX = (self.rightCamStartOffset,
                                  self.rightImg.shape[1] - self.rightCamEndOffset)
        leftImg = self.leftImg[:, leftStartX:leftEndX]
        rightImg = self.rightImg[:, rightStartX:rightEndX]

        # 检测高度是否发生了变化
        newLeftImg = np.zeros_like(leftImg, dtype=np.uint8)
        if self.leftCamHOffset < 0:  # 向上移动
            newLeftImg[:(leftImg.shape[0] + self.leftCamHOffset)] = leftImg[-self.leftCamHOffset:]
        elif self.leftCamHOffset > 0:  # 向下移动
            newLeftImg[self.leftCamHOffset:] = leftImg[:self.leftCamHOffset]
        else:
            newLeftImg = leftImg

        # 拼接图像
        jointImage = np.hstack((newLeftImg, rightImg))
        h, w = jointImage.shape[:2]
        jointImage = cv.resize(jointImage, (w, int(h * cp.heightWidthRatio)),
                               interpolation=cv.INTER_AREA)
        print(f"jointimage.shape : {jointImage.shape[:2]}")
        h, w = jointImage.shape[:2]
        jointImage = jointImage[450:, :]  # 这里从那里获取,去除掉最后的位置,不影响之前的坐标
        return jointImage

    @error_handler_produce
    def move_joint_image_from_web(self, leftCamStartMove, leftCamEndMove,
                                  rightCamStartMove, rightCamEndMove, leftCamHMove):
        """
        界面点击了操作拼接的按钮
        :return:
        """
        self.leftCamStartOffset += leftCamStartMove
        self.leftCamEndOffset += leftCamEndMove
        self.rightCamStartOffset += rightCamStartMove
        self.rightCamEndOffset += rightCamEndMove
        self.leftCamHOffset += leftCamHMove
        newValues = {
            "leftCamStartOffset": self.leftCamStartOffset if self.leftCamStartOffset > 0 else 0,
            "leftCamEndOffset": self.leftCamEndOffset if self.leftCamEndOffset > 0 else 0,
            "rightCamStartOffset": self.rightCamStartOffset if self.rightCamStartOffset > 0 else 0,
            "rightCamEndOffset": self.rightCamEndOffset if self.rightCamEndOffset > 0 else 0,
            "leftCamHOffset": self.leftCamHOffset,
        }
        db.update_data(TableName.imageJointParams, {"key": "1"}, newValues)
        update_tool_data_in_web(ToolDataType.imageJointParams)
        jointImg = self.get_jont_image()
        save_joint_image(jointImg)
        imageSend = image_to_string(jointImg)
        return DataResult(state=True, data=imageSend)

    @error_handler_produce
    def save_total_board_size_from_web(self, width, height):
        """
        保存用来计算高宽比和像素比的物料的宽和高
        :param width:
        :param height:
        :return:
        """
        newValues = {
            "inputWidth": width,
            "inputHeight": height,
        }
        db.update_data(TableName.imageJointParams, {"key": "1"}, newValues)
        return DataResult(state=True)

    def count_board_size_from_web(self) -> DataResult:
        """
        计算物料的宽和高
        :return:
        """
        jointParamsRes = db.get_data_one(TableName.imageJointParams, {"key": "1"})
        if not jointParamsRes:
            data = {}
        else:
            data = jointParamsRes.data
        self.inputWidth = data.get("inputWidth")
        self.inputHeight = data.get("inputHeight")
        if not self.inputWidth or not self.inputHeight:
            return DataResult(info="请先设置物料的宽和高")

        fileNames = []
        for root, dirs, files in os.walk(app_config.countBoardSizeDir):
            if root != app_config.countBoardSizeDir:
                continue
            for file in files:
                if file[-4:] != ".bmp" or "res" in file:
                    continue
                else:
                    fileNames.append(file)
        # 如果文件名为空
        if len(fileNames) == 0:
            return DataResult(info=f"统计图像目录{app_config.countBoardSizeDir}下文件为空,"
                                   f"请先拍摄图片保存到此目录")

        # 这里开始计算
        self.totalboardList = []
        self.imageOkList = []
        self.sizeOkList = []
        self.lastPicFlag = False
        for index, fileName in enumerate(fileNames):
            filePath: str = os.path.join(app_config.countBoardSizeDir, fileName)
            imageSrc = cv.imread(filePath, cv.IMREAD_GRAYSCALE)
            resReult: TotalboardResult = find_totalboard(imageSrc, index + 1)
            _, (w, h), _ = resReult.boxMaster
            totalboardRes = resReult.totalboardColor
            delete_exist_index_image(app_config.countBoardSizeDir, str(index + 1))
            cv.imwrite(os.path.join(app_config.countBoardSizeDir, f"{index + 1}--res.bmp"),
                       totalboardRes)
            self.totalboardList.append([totalboardRes, [round(w, 2), round(h, 2)]])
        else:
            if len(self.totalboardList) == 0:
                return DataResult(info="没有获取到正确的结果,请确认图像目录以及图像正确")
            self.sendIndex = 0
            resultData = self.get_send_data()
            return DataResult(state=True, data=resultData)

    def get_send_data_for_check(self):
        imageSend = self.totalboardList[self.sendIndex][0]
        boardSize = self.totalboardList[self.sendIndex][1]
        boardInfo = f"第 {self.sendIndex + 1} 张图,识别结果: 宽 = {boardSize[0]},高 = {boardSize[1]}"
        calcError = round(boardSize[0] - self.inputWidth, 2), round(boardSize[1] - self.inputHeight, 2)
        return [image_to_string(imageSend), [self.inputWidth, self.inputHeight], boardSize, boardInfo, calcError]

    def get_send_data(self):
        imageSend = self.totalboardList[self.sendIndex][0]
        boardSize = self.totalboardList[self.sendIndex][1]
        boardInfo = f"第 {self.sendIndex + 1} 张图,识别结果: 宽 = {boardSize[0]},高 = {boardSize[1]}"
        return [image_to_string(imageSend), [self.inputWidth, self.inputHeight], boardSize, boardInfo]

    def check_size_error_from_web(self):
        """
        界面上点击了验证物料NG
        """
        if self.lastPicFlag:
            dataSend = self.get_send_data_for_check()
            dataSend[-1] = "已经是最后一张,请点击计算高宽比和像素比进行计算"
            return DataResult(state=True, data=dataSend)
        self.sendIndex += 1
        if self.sendIndex >= len(self.totalboardList):
            self.sendIndex -= 1
            self.lastPicFlag = True
        dataSend = self.get_send_data_for_check()
        return DataResult(state=True, data=dataSend)

    def check_size_ok_from_web(self):
        """
        界面上点击了验证物料OK
        """
        if not self.lastPicFlag:
            self.imageOkList.append(self.totalboardList[self.sendIndex][0])
            self.sizeOkList.append(self.totalboardList[self.sendIndex][1])
        else:
            dataSend = self.get_send_data_for_check()
            dataSend[-1] = "已经是最后一张,请点击计算高宽比和像素比进行计算"
            return DataResult(state=True, data=dataSend)
        self.sendIndex += 1
        if self.sendIndex >= len(self.totalboardList):
            self.sendIndex -= 1
            self.lastPicFlag = True
        dataSend = self.get_send_data_for_check()
        return DataResult(state=True, data=dataSend)

    def total_board_is_ok_from_web(self):
        """
        界面上点击了物料查找OK
        :return:
        """
        if not self.lastPicFlag:
            self.imageOkList.append(self.totalboardList[self.sendIndex][0])
            self.sizeOkList.append(self.totalboardList[self.sendIndex][1])
        else:
            dataSend = self.get_send_data()
            dataSend[-1] = "已经是最后一张,请点击计算高宽比和像素比进行计算"
            return DataResult(state=True, data=dataSend)
        self.sendIndex += 1
        if self.sendIndex >= len(self.totalboardList):
            self.sendIndex -= 1
            self.lastPicFlag = True
        dataSend = self.get_send_data()
        return DataResult(state=True, data=dataSend)

    def total_board_is_error_from_web(self):
        """
        界面上点击了物料查找error
        :return:
        """
        self.sendIndex += 1
        if self.sendIndex >= len(self.totalboardList):
            self.sendIndex -= 1
            self.lastPicFlag = True
            dataSend = self.get_send_data()
            dataSend[-1] = "已经是最后一张,请点击计算高宽比和像素比进行计算"
        else:
            dataSend = self.get_send_data()
        return DataResult(state=True, data=dataSend)

    def check_mm_to_pix(self):
        """
        验证高宽比和像素比
        :return:
        """
        jointParamsRes = db.get_data_one(TableName.imageJointParams, {"key": "1"})
        if not jointParamsRes:
            data = {}
        else:
            data = jointParamsRes.data
        self.inputWidth = data.get("inputWidth")
        self.inputHeight = data.get("inputHeight")
        if not self.inputWidth or not self.inputHeight:
            return DataResult(info="请先设置物料的宽和高")

        fileNames = []
        for root, dirs, files in os.walk(app_config.countBoardSizeDir):
            if root != app_config.countBoardSizeDir:
                continue
            for file in files:
                if file[-4:] != ".bmp" or "res" in file:
                    continue
                else:
                    fileNames.append(file)
        # 如果文件名为空
        if len(fileNames) == 0:
            return DataResult(info=f"统计图像目录{app_config.countBoardSizeDir}下文件为空,"
                                   f"请先拍摄图片保存到此目录")

        # 这里开始计算
        self.totalboardList = []
        self.imageOkList = []
        self.sizeOkList = []
        self.lastPicFlag = False
        for index, fileName in enumerate(fileNames):
            filePath: str = os.path.join(app_config.countBoardSizeDir, fileName)
            imageSrc = cv.imread(filePath, cv.IMREAD_GRAYSCALE)
            resReult: TotalboardResult = find_totalboard(imageSrc, index + 1)
            _, (w, h), _ = resReult.boxMaster
            totalboardRes = resReult.totalboardColor
            delete_exist_index_image(app_config.countBoardSizeDir, str(index + 1))
            cv.imwrite(os.path.join(app_config.countBoardSizeDir, f"{index + 1}--res.bmp"),
                       totalboardRes)
            self.totalboardList.append([totalboardRes, [round(w, 2), round(h, 2)]])
        else:
            if len(self.totalboardList) == 0:
                return DataResult(info="没有获取到正确的结果,请确认图像目录以及图像正确")
            self.sendIndex = 0
            resultData = self.get_send_data_for_check()
            return DataResult(state=True, data=resultData)

    def calc_mm_to_pix_from_web(self):
        """
        界面上点击了计算像素比调用的逻辑
        :return:
        """
        self.reaload_image_joint_params()
        self.widthUsedList = [size[0] for size in self.sizeOkList]
        self.heightUsedList = [size[1] for size in self.sizeOkList]
        widthMean = round(np.mean(self.widthUsedList), 4)
        heightMean = round(np.mean(self.heightUsedList), 4)
        mm2pix = round(widthMean / self.inputWidth, 4)
        mm2pixHeight = round(heightMean / self.inputHeight, 4)  # 像素比越大,证明拍的线条太多了
        # 高度和宽度的比例大的时候,就证明之前的高宽比需要减小,所以从除号
        # 计算新的高宽比
        oldHeightWidthRatio = cp.heightWidthRatio
        self.newMm2pix = mm2pix  # 以宽度的为准
        self.newHeightWidthRatio = round(oldHeightWidthRatio / (mm2pixHeight / mm2pix), 5)
        imageSend = image_to_string(self.totalboardList[self.sendIndex][0])
        boardSize = [widthMean, heightMean]
        inputSize = [self.inputWidth, self.inputHeight]
        showInfo = (f"使用的宽度:{self.widthUsedList},使用高度:{self.heightUsedList},"
                    f"像素比:{self.newMm2pix},高宽比:{self.newHeightWidthRatio}")
        dataSend = [imageSend, boardSize, inputSize, showInfo]
        return DataResult(state=True, data=dataSend)

    def save_mm_to_pix_from_web(self) -> DataResult:
        """
        界面上点击了保存高宽比和像素比
        :return:
        """
        newData = {
            "heightWidthRatio": self.newHeightWidthRatio,
            "mm2pix": self.newMm2pix,
        }
        result = cp.update_params(newData)
        update_tool_data_in_web(ToolDataType.cameraParams)
        return result

    @staticmethod
    def open_count_board_size_dir_from_web() -> DataResult:
        """
        界面上点击了打开图像目录
        :return:
        """
        return open_dir(app_config.countBoardSizeDir)

    @staticmethod
    def get_image_joint_params_from_web() -> DataResult:
        """
        请求参数
        :return:
        """
        update_tool_data_in_web(ToolDataType.imageJointParams)
        return DataResult(state=True)

    def save_joint_params_from_web(self, newData):
        """
        界面上点击了保存图像拼接参数
        :return:
        """
        db.update_data(TableName.imageJointParams, {"key": "1"}, newData)
        self.reaload_image_joint_params()
        if self.leftImg is not None and self.rightImg is not None:
            jointImage = self.get_jont_image()
            save_joint_image(jointImage)
            return DataResult(state=True, data=image_to_string(jointImage))
        else:
            return DataResult(state=True)

    @staticmethod
    def save_camera_params_from_web(newData) -> DataResult:
        return cp.update_params(newData)

    @error_handler_produce
    def open_image_joint_dir_from_web(self) -> DataResult:
        return open_dir(app_config.imageJointDir)


if __name__ == '__main__':
    ct = CameraTool(None, None)
